#!/usr/bin/env python

# Written by John Hoffman
# see LICENSE.txt for license information

from BitTornado import PSYCO
if PSYCO.psyco:
    try:
        import psyco
        assert psyco.__version__ >= 0x010100f0
        psyco.full()
    except:
        pass

import sys
import os
import gc
import wx

from random import seed
from socket import error as socketerror
from threading import Event, Thread
from cStringIO import StringIO
from traceback import print_exc

from LMG.Utility.utility import forceunicode

from BitTornado.__init__ import createPeerID
from BitTornado.natpunch import UPnP_test, _natpmp_supported
from BitTornado.RawServer import RawServer
from BitTornado.ServerPortHandler import MultiHandler
from BitTornado.parseargs import parseargs
from BitTornado.HTTPHandler import HTTPHandler
from BitTornado.BT1.track import Tracker, defaults
from BitTornado.BT1.Encrypter import OPTION_PATTERN

from khashmir.khashmir import Khashmir

from engine import ABCEngine

from Utility.constants import * # pylint: disable-msg = W0611,W0401


def fmttime(n):
    try:
        n = int(n)  # n may be None or too large
        assert n < 5184000  # 60 days
    except:
        return 'downloading'
    m, s = divmod(n, 60)
    h, m = divmod(m, 60)
    return '%d:%02d:%02d' % (h, m, s)

class LaunchMany(Thread):
    def __init__(self):
        Thread.__init__(self, name = "LaunchMany")
        
        try:            
            self.Output = Output()
            
            btconfig = utility.getBTParams()
            if not _natpmp_supported:
                btconfig["natpmp"] = 0
            self.stats_period = btconfig['display_interval']

            self.counter = 0
            self.doneflag = Event()

            self.hashcheck_queue = []
            self.hashcheck_current = None

            # BT RawServer
            self.rawserver = RawServer(self.doneflag, btconfig['timeout_check_interval'], 
                              btconfig['timeout'], ipv6_enable = btconfig['ipv6_enabled'], 
                              failfunc = self.failed, errorfunc = self.exchandler)                   

            self.upnp_type = 0
            
            
            self.listen_port = self.getPort(btconfig)

            if self.listen_port and utility.config.Write('useport', self.listen_port, "int"):
                utility.config.Flush()
                                
            self.handler = MultiHandler(self.rawserver, self.doneflag, btconfig)
            self.rawserver.add_task(self.stats, 0)

            # DHT
            if btconfig["dht"] and self.listen_port:
                utility.dht = Khashmir(btconfig['bind'], self.listen_port, utility.getDatabaseDir(),
                                       ipv6_enable = btconfig['ipv6_enabled'],
                                       upnp = self.upnp_type, natpmp = btconfig['natpmp'])
            else:
                utility.dht = None
                OPTION_PATTERN[63] = False
                
            # Tracker
            self.tracker = None
            if btconfig["tracker"] and self.listen_port:
                self.enableTracker()

        except IOError:
            data = StringIO()
            print_exc(file = data)
            self.Output.exception(data.getvalue())

    def enableTracker(self):
        if self.tracker is None and self.listen_port:
            presets = {'port': self.listen_port,
                       'allowed_dir': os.path.join(utility.configdir.dir_root, 'tracker'),
                       'dfile': os.path.join(utility.getDatabaseDir(), 'tracker.db'),
                      }
            trackconfig, args = parseargs('', defaults, presets = presets)
            self.tracker = Tracker(trackconfig, self.rawserver)
            self.handler.http = HTTPHandler(self.tracker.get, trackconfig['min_time_between_log_flushes'])

    def disableTracker(self):
        if self.tracker is not None:
            self.handler.http.shutdown()
            self.handler.http = None
            self.tracker.save_state()
            self.tracker = None
            
    def getPort(self, btconfig):
        listen_port = None
        
        self.upnp_type = UPnP_test(btconfig['upnp_nat_access'])
        while True:
            try:
                listen_port = self.rawserver.find_and_bind(btconfig['minport'], 
                                                           btconfig['maxport'], 
                                                           btconfig['bind'], 
                                                           ipv6_socket_style = btconfig['ipv6_binds_v4'], 
                                                           upnp = self.upnp_type,
                                                           randomizer = False,
                                                           natpmp = btconfig['natpmp'])
                utility.listen_port = listen_port
                break
            except socketerror, e:
                message = _('listening port not available') + \
                              "\n" + \
                              _('would you like to try another port?') #+ \
                              #"\n\n" + _('Details:') + forceunicode(e.strerror)
                dialog = wx.MessageDialog(None, message, _('Error'), wx.YES_NO|wx.ICON_ERROR)
                result = dialog.ShowModal()
                dialog.Destroy()
                if(result == wx.ID_NO):
                    self.failed(_('listening port not available'))
                    break
                btconfig['minport'] = btconfig['minport'] + 1
                btconfig['maxport'] = btconfig['maxport'] + 1
                    
        return listen_port
            
    def run(self):
        try:
            self.handler.listen_forever()
        except:
            data = StringIO()
            print_exc(file=data)
            self.Output.exception(data.getvalue())
        
        for torrent in utility.torrents["active"].keys():
            torrent.shutdown()

        if self.tracker:
            self.tracker.save_state()
            
        self.rawserver.shutdown()

    def stop(self):
        self.doneflag.set()

    def stats(self):
        for torrent in utility.torrents["active"].keys():
            engine = torrent.connection.engine
            
            if engine is None:
                continue
            
            progress = 0.0
            sizeDone = 0.0
            t = 0
            uprate = 0.0
            dnrate = 0.0
            spew = None
            s = None
            
            if engine.is_dead():
                status = _('stopped')
            elif engine.waiting:
                status = _('waiting')
            elif engine.checking:
                status = engine.btstatus
                progress = engine.status_done
            else:
                stats = engine.statsfunc()
                
                s = stats['stats']
                spew = stats['spew']
                #if engine.seed:
                if engine.dow.finflag.isSet():
                    progress = 1.0
                    if s.numOldSeeds + s.numPeers == 0 and s.last_failed:
                        status = _('connecting')
                    else:
                        status = _('seeding')
                else:
                    if s.numSeeds + s.numPeers:
                        t = stats.get('time')
                        if t == 0:  # unlikely
                            t = 0.01
                        status = _('working')
                    else:
                        t = -1
                        status = _('connecting')
                    dnrate = stats.get('down')
                    progress = stats.get('frac')
                uprate = stats.get('up')
                sizeDone = stats.get('done')

            engine.onUpdateStatus(progress, t, dnrate, uprate, status, s, spew, sizeDone)
        self.rawserver.add_task(self.stats, self.stats_period)
        
    def add(self, torrent):
        c = self.counter
        self.counter += 1
        engine = ABCEngine(torrent, createPeerID())
        torrent.connection.engine = engine
        utility.torrents["active"][torrent] = 1
        engine.start()

    def hashchecksched(self, torrent = None):
        if torrent:
            self.hashcheck_queue.append(torrent)

        # Sort by filesize (smallest should start first)
        self.hashcheck_queue.sort(key = lambda x: x.connection.engine.dow.datalength)
        
        if not self.hashcheck_current:
            self._hashcheck_start()

    def _hashcheck_start(self):
        self.hashcheck_current = self.hashcheck_queue.pop(0)

        engine = self.hashcheck_current.connection.engine
        if engine is not None:
            engine.hashcheck_start(self.hashcheck_callback)
        
    def hashcheck_callback(self):            
        try:
            current = self.hashcheck_current.connection.engine
        except:
            current = None
            
        if current is not None:
            current.hashcheck_callback()
        if self.hashcheck_queue:
            self._hashcheck_start()
        else:
            self.hashcheck_current = None
        
    def was_stopped(self, torrent):
        try:
            self.hashcheck_queue.remove(torrent)
        except:
            pass
        if self.hashcheck_current == torrent:
            self.hashcheck_current = None
            if self.hashcheck_queue:
                self._hashcheck_start()

        torrent.connection.engine = None

        torrent.makeInactive()
        
        # Run the garbage collector to
        # clean up cyclical references
        # (may be left behind when active torrents end)
        gc.collect()

    def hashcheck_was_stopped(self, torrent):
        torrent.connection.engine = None
        torrent.makeInactive()
        
        # Run the garbage collector to
        # clean up cyclical references
        # (may be left behind when active torrents end)
        gc.collect()
        
    def failed(self, s):
        self.Output.message('FAILURE: ' + s)

    def exchandler(self, s):
        self.Output.exception(s)

class Output:
    def __init__(self):
        pass
        
    def exception(self, message):
        sys.stderr.write(message)
    
    def message(self, message):
        message = "-----------------------\n" + message + "\n-----------------------\n"
        sys.stderr.write(message)
